Skip to content

mcp: accept Windows drive letter file roots - #1443

Open
akshita317 wants to merge 3 commits into
modelcontextprotocol:mainfrom
akshita317:mcp-file-roots-windows
Open

akshita317 wants to merge 3 commits into
modelcontextprotocol:mainfrom
akshita317:mcp-file-roots-windows

Conversation

@akshita317

Copy link
Copy Markdown
Contributor

fileRoot turns the client's roots into absolute file paths for FileResourceHandler. On Windows, a root for a drive letter path is a URI such as file:///C:/Users/me/project (RFC 8089, Appendix E.2), whose path is /C:/Users/me/project. fileRoot converted that to \C:\Users\me\project, which is not an absolute path on Windows, so every such root failed with not an absolute path, and with it every read through FileResourceHandler as soon as the client reported any roots. TestFileRoot was skipped on Windows with a "TODO: fix for Windows".

This drops the slash before a drive letter on Windows (fileURIPath). VS Code's form, file:///c%3A/..., decodes to the same path. Nothing changes on other systems.

Tests:

  • TestFileRoot is no longer skipped on Windows and has drive letter cases there (upper and lower case, .., file:/C:/…, c%3A, and file:///foo still rejected). They fail without the change; the existing cases run unchanged elsewhere.
  • TestFileURIPath covers the conversion for windows and linux on any platform, so CI on Linux checks it too.

The other Windows-skipped tests in resource_test.go (TestComputeURIFilepath, TestReadFileResource, …) use POSIX paths in their expectations; I left them for a separate change.

AI assistance: prepared with Claude Code.

akshita317 and others added 2 commits October 24, 2026 01:27
fileRoot turns the client's roots into absolute file paths for
FileResourceHandler. On Windows a root for a drive letter path is a URI
such as file:///C:/Users/me/project (RFC 8089, Appendix E.2), whose path
is /C:/Users/me/project. fileRoot converted that to
\C:\Users\me\project, which is not an absolute path on Windows, so every
such root failed with "not an absolute path", and with it every read
through FileResourceHandler once the client had any roots.

Drop the slash before a drive letter on Windows. VS Code's form,
file:///c%3A/..., decodes to the same path.

TestFileRoot is no longer skipped on Windows and gets drive letter
cases there; they fail without the change. TestFileURIPath covers the
conversion for both Windows and other systems on any platform.

@chrikrah chrikrah left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my perspective this can be merged. Passing goos in is what makes it hold: no workflow here runs on Windows, so the new TestFileRoot cases never execute in CI, while TestFileURIPath does on Linux. This takes up the Windows work 8361df7 deferred for roots only; computeURIFilepath still skips there.

$ go test ./mcp/ -run 'TestFileRoot|TestFileURIPath' -count=1   # head c9e6d6b, merge base 6f47cb7, go1.27.1 linux/amd64
ok  	github.com/modelcontextprotocol/go-sdk/mcp	0.011s

$ <prefix the drive-letter condition in fileURIPath with false> && <same command>
--- FAIL: TestFileURIPath (0.00s)
    resource_test.go:75: fileURIPath("/C:/foo", "windows") = "/C:/foo", want "C:/foo"
FAIL

$ GOOS=windows go test -c -o /dev/null ./mcp/ && go test ./... -count=1
# compiles; 14 packages ok
# not run: the Windows TestFileRoot cases, no Windows host here

Did you run those cases on a Windows machine?

Comment thread mcp/resource.go Outdated
@@ -186,13 +187,28 @@ func fileRoot(root *Root) (_ string, err error) {
return "", errors.New("empty path")
}
// We don't want Localize here: we want an absolute path, which is not local.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: while we're here, let's drop this comment:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 16a042b.

Comment thread mcp/resource_test.go

func TestFileRoot(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("TODO: fix for Windows")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 3 other TODO: fix for Windows skips in this file and 1 more in mcp/mcp_test.go. Since roots now work on Windows, could we address and unskip those too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 16a042b. All three skips in resource_test.go are gone, and so is the TODO: fix for Windows block in TestEndToEnd. That one was failing on Windows because the test itself built the root as "file://" + rootAbs, which gives file://C:\..., not a file URI. It now builds the URI with net/url. Most of the mcp_test.go diff is re-indentation from removing the if block; it reads better with whitespace hidden.

Comment thread mcp/resource.go
if goos == "windows" && len(uriPath) >= 3 && uriPath[0] == '/' && uriPath[2] == ':' && isASCIILetter(uriPath[1]) {
return uriPath[1:]
}
return uriPath

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's handle UNC path as well

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 16a042b. On Windows, a host other than localhost is now a UNC path (RFC 8089, Appendix E.3.1): file://server/share/foo gives \server\share\foo, and file://localhost/C:/foo still gives C:\foo. On other systems the host is ignored as before. fileURIPath takes the host now, and TestFileURIPath and TestFileRoot have cases for both.

A file URI with a host other than localhost names a UNC path on
Windows (RFC 8089, Appendix E.3.1), so file://server/share/dir is now
the root \server\share\dir rather than a path that drops the host.

With file roots working on Windows, the resource tests skipped there
pass, so remove the skips. TestEndToEnd built its root as "file://"
followed by the path, which on Windows is not a file URI
(file://C:\dir); build it with net/url instead.

Drop the stale comment about Localize in fileRoot.
@akshita317

Copy link
Copy Markdown
Contributor Author

@chrikrah Yes. Here they are on a Windows 11 machine, at 16a042b (which also takes the follow-ups nahapetyan-serob asked for: UNC roots, and unskipping the other Windows tests):

$ go test ./mcp/ -run 'TestEndToEnd|TestFileRoot|TestFileURIPath|TestComputeURIFilepath|TestReadFileResource' -count=1 -v   # go1.25.3 windows/amd64
--- PASS: TestEndToEnd (0.01s)
--- PASS: TestFileRoot (0.00s)
--- PASS: TestFileURIPath (0.00s)
--- PASS: TestComputeURIFilepath (0.00s)
--- PASS: TestReadFileResource (0.00s)
--- PASS: TestComputeURIFilepathLocalizeError (0.00s)
ok  	github.com/modelcontextprotocol/go-sdk/mcp	0.395s

$ go test ./mcp/ -count=1
ok  	github.com/modelcontextprotocol/go-sdk/mcp	34.097s

As you say, no CI workflow runs on Windows, so these only run on a machine like this one.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants